
package com.raccoonagents.tools.cascet2d.util;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.text.NumberFormat;

import org.lwjgl.opengl.GL11;

import com.raccoonagents.tools.cascet2d.model.Text;
import com.raccoonagents.tools.cascet2d.model.Vector2f;
import com.raccoonagents.tools.cascet2d.model.exceptions.FontBuildException;

/**
 * @author Mark Bernard
 * date:    26-May-2004
 *
 * Port of NeHe's Lesson 13 to LWJGL
 * Title: Bitmap fonts
 * Uses version 0.9alpha of LWJGL http://www.lwjgl.org/
 *
 * Be sure that the LWJGL libraries are in your classpath
 *
 * Ported directly from the C++ version
 *
 * The main point of this tutorial is to get fonts on the screen.  The original OpenGL did
 * not port directly as it used Windows specific extensions and I could not get some OpenGL
 * commands to work.  In the end, what you see on the screen is the same, but it is written
 * somewhat differently.  I have noted the differences in the code with comments.
 *
 * 2004-10-08: Updated to version 0.92alpha of LWJGL.
 * 2004-12-19: Updated to version 0.94alpha of LWJGL
 * 
 * @author Kari Kuvaja
 * methods and bug fixes to suit cascet2d
 * 
 * 
 */
public class FontUtils {

    private static int texture;

    //build colours for font with alpha transparency
    private static final Color OPAQUE_WHITE = new Color(0xFFFFFFFF, true);
    private static final Color TRANSPARENT_BLACK = new Color(0x00000000, true);
    private final static NumberFormat numberFormat = NumberFormat.getInstance();

    private static int base = 0;                       // Base Display List For The Font Set
    public final static float size = 25.0f;
    public final static Vector2f dimensions = new Vector2f(size / 2, size);
    
    
    
    public static boolean renderText(final float x, final float y, final String text, final float [] color, final float scale, final float opacity) {
    	GL11.glPushMatrix();
        // Position The Text On The Screen
    	GL11.glEnable(GL11.GL_BLEND);
    	GL11.glEnable(GL11.GL_TEXTURE_2D);
    	GL11.glEnable(GL11.GL_COLOR_MATERIAL);
        GL11.glTranslatef(x,y,0.0f);
        
        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
        
        if(color != null){
            GL11.glColor4f(color[0], color[1], color[2], opacity);        	
        }else{
        	GL11.glColor4f(1.0f, 1.0f,1.0f, opacity);
        }
        //format the floating point number to 2 decimal places
        numberFormat.setMaximumFractionDigits(2);
        numberFormat.setMinimumFractionDigits(2);
        glPrint(text, scale);     // Print GL Text To The Screen
    	GL11.glPopMatrix();
    	GL11.glDisable(GL11.GL_TEXTURE_2D);
    	GL11.glDisable(GL11.GL_BLEND);

        return true;
    }

    public static boolean renderText(final Text text) {
    	GL11.glPushMatrix();
        // Position The Text On The Screen
    	GL11.glEnable(GL11.GL_BLEND);
        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
    	GL11.glEnable(GL11.GL_TEXTURE_2D);
    	GL11.glEnable(GL11.GL_COLOR_MATERIAL);
        GL11.glTranslatef(text.getLocation().x,text.getLocation().y,0.0f);
        GL11.glRotatef(text.getRotation(), 0, 0, 1.0f);
        GL11.glColor4f(text.getColor()[0], text.getColor()[1], text.getColor()[2],text.getOpacity());
        GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
        
        for(final String row : text.getRows()){
            //format the floating point number to 2 decimal places
            numberFormat.setMaximumFractionDigits(2);
            numberFormat.setMinimumFractionDigits(2);
            glPrint(row, 1.0f);     // Print GL Text To The Screen        	        	        	
            GL11.glTranslatef(0.0f, - text.getFontSize().y, 0);
        }
    	GL11.glPopMatrix();
    	GL11.glDisable(GL11.GL_TEXTURE_2D);
    	GL11.glDisable(GL11.GL_BLEND);

        return true;
    }
    
    
    
    /* Some liberties had to be taken with this method.  I could not get the glCallLists() to work, so
     * it is done manually instead.
     */
    private static void glPrint(final String msg, final float scale) {  // Custom GL "Print" Routine
        if(msg != null) {
        	GL11.glPushMatrix();
        	GL11.glScalef(scale, scale, 0);
            //GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture);
            for(int i = 0;i < msg.length(); i++) {
                GL11.glCallList(base + msg.charAt(i));
                GL11.glTranslatef(dimensions.x, 0.0f, 0.0f);
            }
            GL11.glPopMatrix();
        }
    }

    public static void init(final String fontName) throws FontBuildException {    	
        buildFont(fontName);
    }

    /* The original tutorial number 13 used a windows specific extension to generate a bitmap
     * for the font.  I had to replace that with a custom bitmap generation that you see below.
     *
     */
    private static void buildFont(final String fontName) {                          // Build Our Bitmap Font
        Font font;                                      // Font object

        /* Note that I have set the font to Courier New.  This font is not guraunteed to be on all
         * systems.  However it is very common so it is likely to be there.  You can replace this name
         * with any named font on your system or use the Java default names that are guraunteed to be there.
         * Also note that this will work well with monospace fonts, but does not look as good with
         * proportional fonts.
         */
        // final String fontName = "Courier New";                // Name of the font to use
        BufferedImage fontImage;                        // image for creating the bitmap
        final int bitmapSize = 512;                           // set the size for the bitmap texture
        boolean sizeFound = false;
        boolean directionSet = false;
        int delta = 0;
        int fontSize = 24;

        /* To find out how much space a Font takes, you need to use a the FontMetrics class.
         * To get the FontMetrics, you need to get it from a Graphics context.  A Graphics context is
         * only available from a displayable surface, ie any class that subclasses Component or any Image.
         * First the font is set on a Graphics object.  Then get the FontMetrics and find out the width
         * and height of the widest character (W).  Then take the largest of the 2 values and find the
         * maximum size font that will fit in the size allocated.
         */
        while(!sizeFound) {
            font = new Font(fontName, Font.PLAIN, fontSize);  // Font Name
            // use BufferedImage.TYPE_4BYTE_ABGR to allow alpha blending
            fontImage = new BufferedImage(bitmapSize, bitmapSize, BufferedImage.TYPE_4BYTE_ABGR);
            final Graphics2D g = (Graphics2D)fontImage.getGraphics();
            g.setFont(font);
            final FontMetrics fm = g.getFontMetrics();
            final int width = fm.stringWidth("W");
            final int height = fm.getHeight();
            final int lineWidth = (width > height) ? width * 16 : height * 16;
            if(!directionSet) {
                if(lineWidth > bitmapSize) {
                    delta = -2;
                }
                else {
                    delta = 2;
                }
                directionSet = true;
            }
            if(delta > 0) {
                if(lineWidth < bitmapSize) {
                    fontSize += delta;
                }
                else {
                    sizeFound = true;
                    fontSize -= delta;
                }
            }
            else if(delta < 0) {
                if(lineWidth > bitmapSize) {
                    fontSize += delta;
                }
                else {
                    sizeFound = true;
                    fontSize -= delta;
                }
            }
        }

        /* Now that a font size has been determined, create the final image, set the font and draw the
         * standard/extended ASCII character set for that font.
         */
        font = new Font(fontName, Font.BOLD, fontSize);  // Font Name
        // use BufferedImage.TYPE_4BYTE_ABGR to allow alpha blending
        fontImage = new BufferedImage(bitmapSize, bitmapSize, BufferedImage.TYPE_4BYTE_ABGR);
        final Graphics2D g = (Graphics2D)fontImage.getGraphics();
        g.setFont(font);
        g.setColor(OPAQUE_WHITE);
        g.setBackground(TRANSPARENT_BLACK);
        final FontMetrics fm = g.getFontMetrics();
        for(int i=0;i < 256;i++) {
            final int x = i % 16;
            final int y = i / 16;
            final char ch[] = {(char)i};
            final String temp = new String(ch);
            g.drawString(temp, (x * 32) + 1, (y * 32) + fm.getAscent());
        }

        /* The following code is taken directly for the LWJGL example code.
         * It takes a Java Image and converts it into an OpenGL texture.
         * This is a very powerful feature as you can use this to generate textures on the fly out
         * of anything.
         */
        //      Flip Image
        final AffineTransform tx = AffineTransform.getScaleInstance(1, -1);
        tx.translate(0, -fontImage.getHeight(null));
        final AffineTransformOp op =
            new AffineTransformOp(tx, AffineTransformOp.TYPE_NEAREST_NEIGHBOR);
        fontImage = op.filter(fontImage, null);

        // Put Image In Memory
        final ByteBuffer scratch =
            ByteBuffer.allocateDirect(
                4 * fontImage.getWidth() * fontImage.getHeight());

        final byte data[] =
            (byte[])fontImage.getRaster().getDataElements(
                0,
                0,
                fontImage.getWidth(),
                fontImage.getHeight(),
                null);
        scratch.clear();
        scratch.put(data);
        scratch.rewind();

        // Create A IntBuffer For Image Address In Memory
        final IntBuffer buf =
            ByteBuffer
                .allocateDirect(4)
                .order(ByteOrder.nativeOrder())
                .asIntBuffer();
        GL11.glGenTextures(buf); // Create Texture In OpenGL

        GL11.glBindTexture(GL11.GL_TEXTURE_2D, buf.get(0));
        // Typical Texture Generation Using Data From The Image

        // Linear Filtering
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
        // Linear Filtering
        GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
        // Generate The Texture
        GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, fontImage.getWidth(), fontImage.getHeight(), 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, scratch);


        texture = buf.get(0);                           // Return Image Address In Memory

        base = GL11.glGenLists(256);                    // Storage For 256 Characters

        /* Generate the display lists.  One for each character in the standard/extended ASCII chart.
         */
        final float textureDelta = 1.0f / 16.0f;
        GL11.glColor3f(0.5f, 1.0f, 1.0f);
        for(int i=0; i < 256;i++) {
            final float u = ((float)(i % 16)) / 16.0f;
            final float v = 1.f - (((float)(i / 16)) / 16.0f);
            GL11.glNewList(base + i, GL11.GL_COMPILE);
            GL11.glPushMatrix();
            GL11.glPushAttrib(GL11.GL_CURRENT_BIT);
            GL11.glBindTexture(GL11.GL_TEXTURE_2D, texture);
            GL11.glBegin(GL11.GL_QUADS);
                GL11.glTexCoord2f(u, v);
                GL11.glVertex3f(0, size, 0.0f);
                GL11.glTexCoord2f((u + textureDelta), v);
                GL11.glVertex3f(size, size, 0.0f);
                GL11.glTexCoord2f((u + textureDelta), v - textureDelta);
                GL11.glVertex3f(size, 0, 0.0f);
                GL11.glTexCoord2f(u, v - textureDelta);
                GL11.glVertex3f(0, 0, 0.0f);
            GL11.glEnd();
            GL11.glPopAttrib();
            GL11.glPopMatrix();
            GL11.glEndList();
        }
    }
}